from gi.repository import Unity, Gio, GObject, Dbusmenu
import sys
import os
import thread
import time
import argparse
from threading import Thread, current_thread, Lock
import google
from unity_pb2 import *
from notify_pb2 import *
from launcher_pb2 import *
from indicator_pb2 import *
import pygtk, gtk
from array import array
import inspect
import pynotify

debug = False

class Logger:
	def __init__(self):
		if not debug: pass
#		window = gtk.Window()
#		window.set_title("UnityProxy Logger")
#		window.set_default_size(640, 480)
		log = gtk.TextView()
		log.set_wrap_mode(gtk.WRAP_WORD)
		self.buffer = log.get_buffer()
#		window.add(log)
#		window.show_all()
#		self.file = open("/home/qba/unity.log", "a")
		self.lock = Lock()
	def log(self, message):
		if not debug: pass
		#with self.lock:
			#self.file = open("/home/kuba/unity.log", "a")
			#self.file.write(str(message) + '\n')
			#self.file.close()
	#		self.buffer.insert_at_cursor(str(message) + '\n')
	def append(self, message):
		if not debug: pass
		#with self.lock:
			#self.file = open("/home/kuba/unity.log", "a")
	#		self.buffer.insert_at_cursor(message)
			#self.file.write(str(message))
			#self.file.close()
class ImplException(Exception):
	def __init__(self, value):
		self.value = value
	def __str__(self):
		return repr(self.value)	
class GLoop(Thread):
	def __init__ (self):
		Thread.__init__(self)
		self.daemon = True
		self.loop = loop = GObject.MainLoop()	
	def run(self):
		GObject.threads_init()
		self.loop.run()
class Byte:
	MAX = 255
	MIN = 0	
class LauncherService:
	def __init__(self):
		self.launchers = {} # map<appname,LauncherEntry>
	def handle(self, evt, res): # LauncherEvent, LauncherEvent.Value
		if evt.name == LauncherEvent.CREATE:
			if evt.value.bool:
				if self.launchers.has_key(evt.app):
					raise ImplException("launcher already exists")
				self.launchers[evt.app] = Unity.LauncherEntry.get_for_app_uri(evt.app)
			else:
				del self.launchers[evt.app]
		else:
			launcher = self.get(evt.app)
			if not evt.HasField('value'):
	#			logger.append("LAUNCHER GET \n")
				self.get_property(launcher, evt.name, res)
			else:
				self.set_property(launcher, evt.name, evt.value)
	def get_property(self, launcher, property, value):
		if property == LauncherEvent.URGENT:
			value.bool = launcher.get_property('urgent')
		elif property == LauncherEvent.PROGRESS:
			value.double = launcher.get_property('progress')
		elif property == LauncherEvent.COUNT:
			value.int = launcher.get_property('count')
		elif property == LauncherEvent.PROGRESS_VISIBLE:
			value.bool = launcher.get_property('progress-visible')
		elif property == LauncherEvent.COUNT_VISIBLE:
			value.bool = launcher.get_property('count-visible')
		elif property == LauncherEvent.APP_URI:
			value.string = launcher.get_property('app-uri')
		else:
			raise ImplException("unsupported launcher property on GET: " + str(evt.name))
	def set_property(self, launcher, property, value):
		if property == LauncherEvent.URGENT:
			launcher.set_property('urgent', value.bool)
		elif property == LauncherEvent.PROGRESS:
			launcher.set_property('progress', value.double)
		elif property == LauncherEvent.COUNT:
			launcher.set_property('count', value.int)
		elif property == LauncherEvent.PROGRESS_VISIBLE:
			launcher.set_property('progress-visible', value.bool)
		elif property == LauncherEvent.COUNT_VISIBLE:
			launcher.set_property('count-visible', value.bool)
		elif property == LauncherEvent.QUICKLIST:
			logger.log("LAUNCHER " + str(launcher.get_property('app-uri')) + " SET QUICKLIST " + str(value.quicklist.id))
			quicklist = quicklistService.get(value.quicklist.id)
			self.get(launcher.get_property('app-uri')).set_property('quicklist', quicklist)
		else:
			raise ImplException("unsupported launcher property on SET: " + str(evt.name))
	def get(self, app):
		return self.launchers[app]
class QuicklistService:
	def __init__(self):
		self.quicklists = {} # <id, Menuitem(quicklist itself, not item)>
		
	def handle(self, evt, res): # QuicklistEvent
		if evt.type == QuicklistEvent.CREATE:
			logger.log("QUICKLIST " + str(evt.quicklist.id) + " CREATE")
			self.create(evt.quicklist)
			
		elif evt.type == QuicklistEvent.DELETE:
			logger.log("QUICKLIST " + str(evt.quicklist.id) + " DELETE")
			del self.quicklists[evt.quicklist.id]
		else:
#			if not evt.HasField('item'):
#				raise ImplException("no item specified while performing item " + evt.type + " to quicklist")
			quicklist = self.get(evt.quicklist.id)
			if evt.type == QuicklistEvent.ADD:
				for proto in evt.item:
					logger.log("QUICKLIST " + str(evt.quicklist.id) + " ADD ITEM " + str(proto.id))
					item = itemService.get(proto.id)
					#quicklist.child_append(item)
					if evt.HasField('index'):
						quicklist.child_add_position(item, evt.index)
						#quicklist.child_reorder(item, evt.index)
					else: quicklist.child_append(item)
			elif evt.type == QuicklistEvent.REMOVE:
				for proto in evt.item:
					logger.log("QUICKLIST " + str(evt.quicklist.id) + " REMOVE ITEM " + str(proto.id))
					item = itemService.get(proto.id)
					quicklist.child_delete(item)
			elif evt.type == QuicklistEvent.SET:
				raise ImplException("not supported yet")
				for proto in evt.item:
					item = itemService.get(proto.id)
					to_remove = quicklist.get_children()[evt.index]
					quicklist.child_delete(to_remove)
					quicklist.child_add_position(evt.index, item)
					
			else:
				raise ImplException('unsupported QuicklistEvent type: ' + str(evt.type))
		
	def get(self, id):
		try:
			return self.quicklists[id]
		except KeyError:
			raise ImplException("no item with id " + str(id) + " found")
		
	def create(self, proto):
		quicklist = Dbusmenu.Menuitem.new()
		for itemProto in proto.item:
			item = itemService.get(itemProto.id)
			quicklist.child_append(item)
		self.quicklists[proto.id] = quicklist
		return quicklist	
class ItemService:
	def __init__(self):
		self.items = {} # <id, Menuitem>
		self.lock = Lock()
	def handle(self, evt, res):
		with self.lock:
			if evt.type == ItemEvent.CREATE:
				self.create(evt.item)
			elif evt.type == ItemEvent.DELETE:
				del self.items[evt.item.id]
			else:
				if not evt.HasField('name'):
					raise ImplException("no property name specified while getting or setting property")
				item = self.get(evt.item.id)
				if evt.type == ItemEvent.GET:
					value = res
					
					if evt.name == ItemEvent.VISIBLE:
						value.bool = item.property_get_bool('visible')
						
					elif evt.name == ItemEvent.ENABLED:
						value.bool = item.property_get_bool('enabled')
						
					elif evt.name == ItemEvent.LABEL:
						value.string = item.property_get('label')
						
					elif evt.name == ItemEvent.SELECTED:
						value.bool = bool(item.property_get_int('toggle-state'))
						
					else:
						raise ImplException("unknown item property type while GET: " + str(evt.name))
						
				elif evt.type == ItemEvent.SET:
					if debug: logger.append("ITEM " + str(evt.item.id) + " SET ")
					if not evt.HasField('value'):
						raise ImplException("no value set while setting item property value")
					
					if evt.name == ItemEvent.VISIBLE:
						if debug: logger.log("VISIBLE " + str(evt.value.bool))
						item.property_set_bool('visible', evt.value.bool)
						if item.property_get_bool('visible') != evt.value.bool:
							raise Exception("fucking error 1")
						
					elif evt.name == ItemEvent.ENABLED:
						if debug: logger.log("ENABLED " + str(evt.value.bool))
						item.property_set_bool('enabled', evt.value.bool)
						
					elif evt.name == ItemEvent.LABEL:
						if debug: logger.log("LABEL " + str(evt.value.string))
						item.property_set('label', evt.value.string)
						
					elif evt.name == ItemEvent.SELECTED:
						item.property_set_int('toggle-state', int(evt.value.bool))
						# temporarily
						item.property_set_int('toggle-state', int(evt.value.bool))
						evt.value.bool = bool(item.property_get_int('toggle-state'))
						if debug: logger.log("SELECTED " + str(evt.value.bool) + " or " + str(int(evt.value.bool)))
						if item.property_get_int('toggle-state') != int(evt.value.bool):
							raise Exception("fucking error 2")
					else: raise ImplException("unknown item property type while SET: " + evt.name)
						
				else:
					raise ImplException("unsupported item event type: " + evt.type)		
	def get(self, id):
		try:
			return self.items[id]
		except KeyError:
			raise ImplException("item with id " + str(id) + " not found")
#			items[id] = item = Dbusmenu.Menuitem.new()
#			return item
	def create(self, proto):
		item = Dbusmenu.Menuitem.new()
		item.property_set ('label', proto.label)
		item.property_set_bool ('enabled', proto.enabled)
		item.property_set_bool('visible', proto.visible)
		if proto.HasField('selected'):
			item.property_set_int('toggle-state', int(proto.selected))
		if proto.HasField('type'):
			if proto.type == ItemPb.SEPARATOR:
				item.property_set('type', 'separator')
			else:
				item.connect('item-activated', self.trigger_signal, proto.id)	
				if proto.type == ItemPb.SELECT:
					item.property_set('toggle-type', 'checkmark')
				elif proto.type == ItemPb.RADIO:
					item.property_set('toggle-type', 'radio')
		self.items[proto.id] = item
		return item
	def trigger_signal(self, item, num, id):
		with self.lock:
			logger.log("signal appeared: " + str(id))
			toggleType = item.property_get('toggle-type')
			if toggleType == 'checkmark' or toggleType == 'radio':
				state = item.property_get_int('toggle-state')
				if toggleType == 'checkmark':
#					item.property_set_int('toggle-state', int(not bool(state)))
					self.fire_event(id, not bool(state))
				else:
#					item.property_set_int('toggle-state', 1)
					self.fire_event(id, True)
			else:
				try:
					self.fire_event(id, None)
				except Exception, e:
					logger.log("event trigger failure: " + str(e))
	def fire_event(self, id, value):
		logger.log('item event fired for id: ' + str(id))
		res = Response()
		signal = res.Extensions[item_signal]
		signal.item.id = id
		if value != None: signal.item.selected = value
		send_signal(res)		
class NotifyService:
	def __init__(self):
		self.notifications = {} # map<id,Notification>
	def handle(self, evt):
		if evt.type == NotifyEvent.SHOW:
			self.notify(evt.notification)
		elif evt.type == NotifyEvent.UPDATE:
			pass
			#n = self.notifications[evt.notification.id]
			#proto = evt.notification
			#n.update(proto.summary, proto.body, proto.icon)
#			if not n:
#				self.notifications[proto.id] = self.notify(proto.summary, proto.body, proto.icon)
#			else:
#				n.update(proto.summary, proto.body, proto.icon)
#				n.show()
		elif evt.type == NotifyEvent.CREATE:
			self.create(evt.notification)
		elif evt.type == NotifyEvent.DELETE:
			self.delete(evt.notification)
		else:
			raise ImplException("unsupported type of NotifyEvent")
	def get(self, id):
		return self.notifications[id]
	def notify(self, proto):
		n = self.notifications[proto.id]
		n.update(proto.summary, proto.body, proto.icon)
		if proto.HasField('append'):
			n.set_hint_string ('x-canonical-append', str(proto.append))
		n.show()
	def create(self, proto):
		logger.log("creating notification: " + str(proto))
		n = pynotify.Notification(proto.summary, proto.body, proto.icon)
		n.set_hint_string ("x-canonical-append", str(proto.append))
		self.notifications[proto.id] = n
		return n
	def delete(self, proto):
		del self.notifications[proto.id]
class IndicatorService:
	def __init__(self):
		self.indicators = {}
	def create(self, proto):
		pass
	def delete(self, proto):
		pass
indicatorService = IndicatorService()
itemService = ItemService()
quicklistService = QuicklistService()
launcherService = LauncherService()
notifyService = NotifyService()

out_lock = Lock()
pynotify.init("LibUnity Java API")

def handle(req, res):
#	logger.append("request received: ")
	if req.HasExtension(launcher_event):
		launcherService.handle(req.Extensions[launcher_event], res.Extensions[launcher_response])
	elif req.HasExtension(item_event):
		itemService.handle(req.Extensions[item_event], res.Extensions[item_response])
	elif req.HasExtension(quicklist_event):
		quicklistService.handle(req.Extensions[quicklist_event], res.Extensions[quicklist_response])
	elif req.HasExtension(notify_event):
		notifyService.handle(req.Extensions[notify_event])
	elif req.HasExtension(indicator_event):
		pass
	else:
		raise ImplException("unknown kind of request")
	
def send_signal(pb):
	pb.type = Response.SIGNAL
	send_response(pb)

def send_response(pb):
	with out_lock:
		def write_size(size):
			if size >= Byte.MAX:
				for a in range(size / Byte.MAX):
					sys.stdout.write(chr(Byte.MAX))
				sys.stdout.write(chr(size % Byte.MAX))
			else: sys.stdout.write(chr(size))
		response = pb.SerializeToString()
#		logger.log("writing message: " + str(pb))
		write_size(len(response))
		sys.stdout.write(response)
		sys.stdout.flush()
	
def receive_request():
	def read_size():
		size = 0
		while True:
			#logger.log("reading next size pointer")
			b = sys.stdin.read(1)
			if not b:
				logger.log("end of pipe reached, exiting program")
				time.sleep(100)
				#os._exit(1)
			#logger.log("just read next size pointer: " + str(ord(b)))
			p = int(str(ord(b)))
			size += p
			#logger.log("after merging size")
			if p < Byte.MAX:
				#logger.log("size is known so lets read the message: " + str(size)) 
				return size
	#logger.log("waiting for next request...")
	size = read_size()
	req = Request()
	data = sys.stdin.read(size)
	req.ParseFromString(data)
	return req 

if __name__ == '__main__':
	def accept(req):
		try:
			res = Response()
			handle(req, res)
			send_response(res)
		except ImplException, e:
			res.exception.message = str(e)
			res.exception.impl = True
			logger.log("impl exception, sending error response: " + str(res)) 
			send_response(res)
		except Exception, e:
			res.exception.message = str(e)
			logger.log("error occurred (" + str(e.__class__) + "), sending error response: " + str(res))
			send_response(res)
	#sys.stdin = open("file.txt")
	logger = Logger()
	gloop = GLoop()
	gloop.start()
	logger.log("sending check byte: " + str(47))
	sys.stdout.write(chr(47))
	b = sys.stdin.read(1)
	c = ord(b)
	logger.log("check byte: " + str(c))
	while True:
		try:
			req = receive_request()
			GObject.idle_add(accept, req)
		except ImplException, e:
			res.exception.message = str(e)
			res.exception.impl = True
			logger.log("impl exception, sending error response: " + str(res)) 
			send_response(res)
		except Exception, e:
			res.exception.message = str(e)
			logger.log("error occurred (" + str(e.__class__) + "), sending error response: " + str(res)) 
			send_response(res)



